<?php

/*
 * This file is part of SwiftMailer. (c) 2004-2009 Chris Corbyn For the full copyright and license information, please view the LICENSE file that was distributed with this source code.
 */

/**
 * Defines the grammar to use for validation, implements the RFC 2822 (and friends) ABNF grammar definitions.
 * 
 * @package Swift
 * @subpackage Mime
 * @author Fabien Potencier
 * @author Chris Corbyn
 */
class Swift_Mime_Grammar {
	/**
	 * Special characters used in the syntax which need to be escaped.
	 * 
	 * @var string[]
	 * @access private
	 */
	private static $_specials = array ();
	
	/**
	 * Tokens defined in RFC 2822 (and some related RFCs).
	 * 
	 * @var string[]
	 * @access private
	 */
	private static $_grammar = array ();
	
	/**
	 * Initialize some RFC 2822 (and friends) ABNF grammar definitions.
	 * 
	 * @access protected
	 */
	public function __construct() {
		$this->init ();
	}
	public function __wakeup() {
		$this->init ();
	}
	protected function init() {
		if (count ( self::$_specials ) > 0) {
			return;
		}
		
		self::$_specials = array (
				'(',
				')',
				'<',
				'>',
				'[',
				']',
				':',
				';',
				'@',
				',',
				'.',
				'"' 
		);
		
		/**
		 * * Refer to RFC 2822 for ABNF grammar **
		 */
		
		// All basic building blocks
		self::$_grammar ['NO-WS-CTL'] = '[\x01-\x08\x0B\x0C\x0E-\x19\x7F]';
		self::$_grammar ['WSP'] = '[ \t]';
		self::$_grammar ['CRLF'] = '(?:\r\n)';
		self::$_grammar ['FWS'] = '(?:(?:' . self::$_grammar ['WSP'] . '*' . self::$_grammar ['CRLF'] . ')?' . self::$_grammar ['WSP'] . ')';
		self::$_grammar ['text'] = '[\x00-\x08\x0B\x0C\x0E-\x7F]';
		self::$_grammar ['quoted-pair'] = '(?:\\\\' . self::$_grammar ['text'] . ')';
		self::$_grammar ['ctext'] = '(?:' . self::$_grammar ['NO-WS-CTL'] . '|[\x21-\x27\x2A-\x5B\x5D-\x7E])';
		// Uses recursive PCRE (?1) -- could be a weak point??
		self::$_grammar ['ccontent'] = '(?:' . self::$_grammar ['ctext'] . '|' . self::$_grammar ['quoted-pair'] . '|(?1))';
		self::$_grammar ['comment'] = '(\((?:' . self::$_grammar ['FWS'] . '|' . self::$_grammar ['ccontent'] . ')*' . self::$_grammar ['FWS'] . '?\))';
		self::$_grammar ['CFWS'] = '(?:(?:' . self::$_grammar ['FWS'] . '?' . self::$_grammar ['comment'] . ')*(?:(?:' . self::$_grammar ['FWS'] . '?' . self::$_grammar ['comment'] . ')|' . self::$_grammar ['FWS'] . '))';
		self::$_grammar ['qtext'] = '(?:' . self::$_grammar ['NO-WS-CTL'] . '|[\x21\x23-\x5B\x5D-\x7E])';
		self::$_grammar ['qcontent'] = '(?:' . self::$_grammar ['qtext'] . '|' . self::$_grammar ['quoted-pair'] . ')';
		self::$_grammar ['quoted-string'] = '(?:' . self::$_grammar ['CFWS'] . '?"' . '(' . self::$_grammar ['FWS'] . '?' . self::$_grammar ['qcontent'] . ')*' . self::$_grammar ['FWS'] . '?"' . self::$_grammar ['CFWS'] . '?)';
		self::$_grammar ['atext'] = '[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]';
		self::$_grammar ['atom'] = '(?:' . self::$_grammar ['CFWS'] . '?' . self::$_grammar ['atext'] . '+' . self::$_grammar ['CFWS'] . '?)';
		self::$_grammar ['dot-atom-text'] = '(?:' . self::$_grammar ['atext'] . '+' . '(\.' . self::$_grammar ['atext'] . '+)*)';
		self::$_grammar ['dot-atom'] = '(?:' . self::$_grammar ['CFWS'] . '?' . self::$_grammar ['dot-atom-text'] . '+' . self::$_grammar ['CFWS'] . '?)';
		self::$_grammar ['word'] = '(?:' . self::$_grammar ['atom'] . '|' . self::$_grammar ['quoted-string'] . ')';
		self::$_grammar ['phrase'] = '(?:' . self::$_grammar ['word'] . '+?)';
		self::$_grammar ['no-fold-quote'] = '(?:"(?:' . self::$_grammar ['qtext'] . '|' . self::$_grammar ['quoted-pair'] . ')*")';
		self::$_grammar ['dtext'] = '(?:' . self::$_grammar ['NO-WS-CTL'] . '|[\x21-\x5A\x5E-\x7E])';
		self::$_grammar ['no-fold-literal'] = '(?:\[(?:' . self::$_grammar ['dtext'] . '|' . self::$_grammar ['quoted-pair'] . ')*\])';
		
		// Message IDs
		self::$_grammar ['id-left'] = '(?:' . self::$_grammar ['dot-atom-text'] . '|' . self::$_grammar ['no-fold-quote'] . ')';
		self::$_grammar ['id-right'] = '(?:' . self::$_grammar ['dot-atom-text'] . '|' . self::$_grammar ['no-fold-literal'] . ')';
		
		// Addresses, mailboxes and paths
		self::$_grammar ['local-part'] = '(?:' . self::$_grammar ['dot-atom'] . '|' . self::$_grammar ['quoted-string'] . ')';
		self::$_grammar ['dcontent'] = '(?:' . self::$_grammar ['dtext'] . '|' . self::$_grammar ['quoted-pair'] . ')';
		self::$_grammar ['domain-literal'] = '(?:' . self::$_grammar ['CFWS'] . '?\[(' . self::$_grammar ['FWS'] . '?' . self::$_grammar ['dcontent'] . ')*?' . self::$_grammar ['FWS'] . '?\]' . self::$_grammar ['CFWS'] . '?)';
		self::$_grammar ['domain'] = '(?:' . self::$_grammar ['dot-atom'] . '|' . self::$_grammar ['domain-literal'] . ')';
		self::$_grammar ['addr-spec'] = '(?:' . self::$_grammar ['local-part'] . '@' . self::$_grammar ['domain'] . ')';
	}
	
	/**
	 * Get the grammar defined for $name token.
	 * 
	 * @param string $name
	 *        	execatly as written in the RFC
	 * @return string
	 */
	public function getDefinition($name) {
		if (array_key_exists ( $name, self::$_grammar )) {
			return self::$_grammar [$name];
		} else {
			throw new Swift_RfcComplianceException ( "No such grammar '" . $name . "' defined." );
		}
	}
	
	/**
	 * Returns the tokens defined in RFC 2822 (and some related RFCs).
	 * 
	 * @return array
	 */
	public function getGrammarDefinitions() {
		return self::$_grammar;
	}
	
	/**
	 * Returns the current special characters used in the syntax which need to be escaped.
	 * 
	 * @return array
	 */
	public function getSpecials() {
		return self::$_specials;
	}
	
	/**
	 * Escape special characters in a string (convert to quoted-pairs).
	 * 
	 * @param string $token        	
	 * @param string[] $include
	 *        	additonal chars to escape
	 * @param string[] $exclude
	 *        	chars from escaping
	 * @return string
	 */
	public function escapeSpecials($token, $include = array(), $exclude = array()) {
		foreach ( array_merge ( array (
				'\\' 
		), array_diff ( self::$_specials, $exclude ), $include ) as $char ) {
			$token = str_replace ( $char, '\\' . $char, $token );
		}
		
		return $token;
	}
}
